package cn.itcast.redis;

import cn.itcast.SpringBeanUtil;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldType;

/**
 * 用户签到记录  第一种方式
 * https://www.cnblogs.com/liujiduo/p/10396020.html
 * https://mp.weixin.qq.com/s/0fFqH83waCBiFZlM0lWIuQ
 * u8 8位长的无符号整数  i16 16位长的有符号整数
 * BitFieldType.unsigned(31)这里无符号整数的位数，默认我使用了setbit，值偏移量date最大31，即使用31位无符号整数记录,可以修改
 * 在统计数据的位置也要保持一致，使用31位无符号整数
 * create by zhangtian3 on 2019/7/1
 */
@Component
public class UserSignRecordRedis {
    //由于当前class不在spring boot框架内（不在web项目中）所以无法使用autowired，使用此种方法进行注入
    private static RedisTemplate<String, String> template = (RedisTemplate<String, String>) SpringBeanUtil.getBean("redisTemplate");

    /**
     * 用户签到
     * @param uid  用户ID
     * @param date 日期
     * @return 之前的签到状态
     */
    public boolean doSign(int uid, LocalDate date) {
        int offset = date.getDayOfMonth() - 1;
        boolean result = template.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = template.getStringSerializer();
                return connection.setBit(serializer.serialize(buildSignKey(uid, date)), offset, true) ;
            }
        });

        return result ;
    }

    /**
     * 检查用户是否签到
     * @param uid  用户ID
     * @param date 日期
     * @return 当前的签到状态
     */
    public boolean checkSign(long uid, LocalDate date) {
        int offset = date.getDayOfMonth() - 1;
        boolean result = template.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = template.getStringSerializer();
                return connection.getBit(serializer.serialize(buildSignKey(uid, date)), offset) ;
            }
        });

        return result ;
    }

    /**
     * 获取用户签到次数
     * @param uid  用户ID
     * @param date 日期
     * @return 当前的签到次数
     */
    public long getSignCount(int uid, LocalDate date) {
        Long result = template.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = template.getStringSerializer();
                return connection.bitCount(serializer.serialize(buildSignKey(uid, date))) ;
            }
        });

        return result ;
    }

    /**
     * 获取当月连续签到次数
     * 这里连续签到业务逻辑为：从今天开始（包括今天签到），之后往后倒推连续的不间断天数，根据天数设置奖励
     * @param uid  用户ID
     * @param date 日期
     * @return 当月连续签到次数
     */
    public long getContinuousSignCount(int uid, LocalDate date) {
        int signCount = 0;
        // String type = String.format("u%d", date.getDayOfMonth());

        List<Long> list = template.execute(new RedisCallback<List<Long>>() {
            @Override
            public List<Long> doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = template.getStringSerializer();
                // 31可以使用date.getDayOfMonth()设置位数，也可以考虑闰年
                BitFieldSubCommands.BitFieldGetBuilder get = BitFieldSubCommands.create().get(BitFieldType.unsigned(31)) ; // 一月最多31天 最少28天 使用31位

                return connection.bitField(serializer.serialize(buildSignKey(uid, date)), get.valueAt(0)) ;// 偏移量为0表示整个bit对应的整数
            }
        });

        //jedis.bitfield(buildSignKey(uid, date), "GET", type, "0");
        if (list != null && list.size() > 0) {
            // 取低位连续不为0的个数即为连续签到次数，需考虑当天尚未签到的情况
            long v = list.get(0) == null ? 0 : list.get(0);
            v >>= (date.lengthOfMonth() - date.getDayOfMonth()) ;// 将偏移量调整到今天的位置获取取偏移量的时候取今天的偏移量get.valueAt(date.getDayOfMonth())
            for (int i = 0; i < date.getDayOfMonth(); i++) {
                if (v >> 1 << 1 == v) {// true 表示未签到  false 表示已签到
                    // 低位为0且非当天说明连续签到中断了  从今天倒序  签到中断
                    if (i > 0){
                        break;
                    }
                } else {
                    signCount += 1;
                }

                v >>= 1;
            }
        }

        return signCount;
    }

    /**
     * 获取当月首次签到日期
     * @param uid  用户ID
     * @param date 日期
     * @return 首次签到日期
     */
    public LocalDate getFirstSignDate(int uid, LocalDate date) {
        Long result = template.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = template.getStringSerializer();
                return connection.bitPos(serializer.serialize(buildSignKey(uid, date)), true) ;
            }
        });

        return result < 0 ? null : date.withDayOfMonth((int) (result + 1));
    }

    /**
     * 获取当月首次断签日期
     * @param uid  用户ID
     * @param date 日期
     * @return 首次断签日期
     */
    public LocalDate getFirstUnSignDate(int uid, LocalDate date) {
        Long result = template.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = template.getStringSerializer();
                return connection.bitPos(serializer.serialize(buildSignKey(uid, date)), false) ;
            }
        });

        return result < 0 ? null : date.withDayOfMonth((int) (result + 1));
    }

    /**
     * 获取当月签到情况
     * @param uid  用户ID
     * @param date 日期
     * @return Key为签到日期，Value为签到状态的Map
     */
    public Map<String, Boolean> getSignInfo(int uid, LocalDate date) {
        Map<String, Boolean> signMap = new TreeMap<>();
        // String type = String.format("u%d", date.lengthOfMonth());

        List<Long> list = template.execute(new RedisCallback<List<Long>>() {
            @Override
            public List<Long> doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = template.getStringSerializer();
                // 31可以使用date.getDayOfMonth()设置位数，也可以考虑闰年
                BitFieldSubCommands.BitFieldGetBuilder get = BitFieldSubCommands.create().get(BitFieldType.unsigned(31)) ; // 一月最多31天 最少28天 使用31位

                return connection.bitField(serializer.serialize(buildSignKey(uid, date)), get.valueAt(0)) ;// 偏移量为0表示整个bit对应的整数
            }
        });

        // jedis.bitfield(buildSignKey(uid, date), "GET", type, "0");
        if (list != null && list.size() > 0) {
            // 由低位到高位，为0表示未签，为1表示已签
            long v = list.get(0) == null ? 0 : list.get(0);
            for (int i = date.lengthOfMonth(); i > 0; i--) {
                v >>= 1;
                LocalDate d = date.withDayOfMonth(i);
                signMap.put(formatDate(d, "yyyy-MM-dd"), v >> 1 << 1 != v); // true 表示已签到  false 表示未签到
            }
        }

        return signMap;
    }

    // 时间格式化
    public static String formatDate(LocalDate date) {
        return formatDate(date, "yyyyMM");
    }

    // 时间格式化
    public static String formatDate(LocalDate date, String pattern) {
        return date.format(DateTimeFormatter.ofPattern(pattern));
    }

    // 键值格式化
    public static String buildSignKey(long uid, LocalDate date) {
        return String.format("u:sign:%d:%s", uid, formatDate(date));
    }

    /**
     * 删除签到记录
     * @param uid
     * @param date
     * @return
     */
    public String delSignInfo(long uid, LocalDate date) {
        Long result = template.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = template.getStringSerializer();
                return connection.del(serializer.serialize(buildSignKey(uid, date))) ;
            }
        });

        return result > 0 ? "删除成功" : "删除失败" ;
    }
}
